#include <nsboundingbox.h>
#include <nsmesh.h>


NSBoundingBox::NSBoundingBox(Type type): boxType(type),
	positionBuf(0),
	transformBuf(0),
	minCoords(),
	maxCoords()
{
	glGenBuffers(1,&positionBuf);
	if (boxType == OBB)
		glGenBuffers(1,&transformBuf);
}

NSBoundingBox::~NSBoundingBox()
{
	glDeleteBuffers(1,&positionBuf);
	glDeleteBuffers(1,&transformBuf);
}

void NSBoundingBox::calculate(const NSMesh * mesh, const NSMatrix4Df & transform)
{
	switch (boxType)
	{
	case (OBB):
		_calculateOBB(mesh);
		break;
	case (AABB):
		_calculateAABB(mesh,transform);
		break;
	}
}

void NSBoundingBox::draw(const NSMatrix4Df & transform)
{
	switch (boxType)
	{
	case (OBB):
		_drawOBB(transform);
		break;
	case (AABB):
		_drawAABB();
		break;
	}
}

NSBoundingBox::Distances NSBoundingBox::getDistancesFromPoint(const NSVec3Df & point)
{
	Distances plusMinus;

	plusMinus.positive.x = maxCoords.x - point.x;
	plusMinus.positive.y = maxCoords.y - point.y;
	plusMinus.positive.z = maxCoords.z - point.z;

	plusMinus.negative.x = point.x - minCoords.x;
	plusMinus.negative.y = point.y - minCoords.y;
	plusMinus.negative.z = point.z - minCoords.z;

	return plusMinus;
}

NSBoundingBox::Type NSBoundingBox::getType()
{
	return boxType;
}

void NSBoundingBox::setType(Type type)
{
	boxType = type;
}

void NSBoundingBox::update(const NSMesh * mesh, const NSMatrix4Df & transform)
{
	calculate(mesh,transform);
}

void NSBoundingBox::_calculateAABB(const NSMesh * mesh, const NSMatrix4Df & transform)
{
	NSMesh::ConstSubMeshIter iter = mesh->subMeshBegin();
	while (iter != mesh->subMeshEnd())
	{
		minCoords = maxCoords = transform * (*iter)->positions[(*iter)->indices[0]]; // assign the first vertice to min and max
		for (unsigned int i = 0; i < (*iter)->indices.size(); ++i)
		{
			unsigned int indice = (*iter)->indices[i];
			NSVec3Df transPos = transform * (*iter)->positions[indice];

			if ( transPos.x < minCoords.x )
				minCoords.x = transPos.x;
			if ( transPos.x > maxCoords.x )
				maxCoords.x = transPos.x;
			if ( transPos.y < minCoords.y )
				minCoords.y = transPos.y;
			if ( transPos.y > maxCoords.y )
				maxCoords.y = transPos.y;
			if ( transPos.z < minCoords.z )
				minCoords.z = transPos.z;
			if ( transPos.z > maxCoords.z )
				maxCoords.z = transPos.z;
		}
		++iter;
	}

	_populateVerts();
}

void NSBoundingBox::_calculateOBB( const NSMesh * mesh)
{
	NSMesh::ConstSubMeshIter iter = mesh->subMeshBegin();
	while (iter != mesh->subMeshEnd())
	{
		for (unsigned int i = 0; i < (*iter)->indices.size(); ++i)
		{
			unsigned int indice = (*iter)->indices[i];
			if ( (*iter)->positions[indice].x < minCoords.x )
				minCoords.x = (*iter)->positions[indice].x;
			if ( (*iter)->positions[indice].x > maxCoords.x )
				maxCoords.x = (*iter)->positions[indice].x;
			if ( (*iter)->positions[indice].y < minCoords.y )
				minCoords.y = (*iter)->positions[indice].y;
			if ( (*iter)->positions[indice].y > maxCoords.y )
				maxCoords.y = (*iter)->positions[indice].y;
			if ( (*iter)->positions[indice].z < minCoords.z )
				minCoords.z = (*iter)->positions[indice].z;
			if ( (*iter)->positions[indice].z > maxCoords.z )
				maxCoords.z = (*iter)->positions[indice].z;
		}
		++iter;
	}

	_populateVerts();

	glBindBuffer(GL_ARRAY_BUFFER, positionBuf);
	glBufferData(GL_ARRAY_BUFFER, sizeof(NSVec3Df) * verts.size(), &verts[0], GL_STATIC_DRAW);
}

void NSBoundingBox::_populateVerts()
{
	verts.clear();
	verts.push_back(minCoords);
	verts.push_back(NSVec3Df(minCoords.x,minCoords.y,maxCoords.z));
	verts.push_back(NSVec3Df(minCoords.x,minCoords.y,maxCoords.z));
	verts.push_back(NSVec3Df(minCoords.x,maxCoords.y,maxCoords.z));
	verts.push_back(NSVec3Df(minCoords.x,maxCoords.y,maxCoords.z));
	verts.push_back(NSVec3Df(minCoords.x,maxCoords.y,minCoords.z));
	verts.push_back(NSVec3Df(minCoords.x,maxCoords.y,minCoords.z));
	verts.push_back(minCoords);

	verts.push_back(NSVec3Df(maxCoords.x,minCoords.y,minCoords.z));
	verts.push_back(NSVec3Df(maxCoords.x,minCoords.y,maxCoords.z));
	verts.push_back(NSVec3Df(maxCoords.x,minCoords.y,maxCoords.z));
	verts.push_back(maxCoords);
	verts.push_back(maxCoords);
	verts.push_back(NSVec3Df(maxCoords.x,maxCoords.y,minCoords.z));
	verts.push_back(NSVec3Df(maxCoords.x,maxCoords.y,minCoords.z));
	verts.push_back(NSVec3Df(maxCoords.x,minCoords.y,minCoords.z));

	verts.push_back(minCoords);
	verts.push_back(NSVec3Df(maxCoords.x,minCoords.y,minCoords.z));
	verts.push_back(NSVec3Df(minCoords.x,maxCoords.y,minCoords.z));
	verts.push_back(NSVec3Df(maxCoords.x,maxCoords.y,minCoords.z));
	verts.push_back(NSVec3Df(minCoords.x,maxCoords.y,maxCoords.z));
	verts.push_back(maxCoords);
	verts.push_back(NSVec3Df(minCoords.x,minCoords.y,maxCoords.z));
	verts.push_back(NSVec3Df(maxCoords.x,minCoords.y,maxCoords.z));
}

void NSBoundingBox::_drawAABB()
{
	glBindBuffer(GL_ARRAY_BUFFER, positionBuf);
	glBufferData(GL_ARRAY_BUFFER, sizeof(NSVec3Df) * verts.size(), &verts[0], GL_DYNAMIC_DRAW);

	glEnableVertexAttribArray(POS_ATT);
	glVertexAttribPointer(POS_ATT, 3, GL_FLOAT, GL_FALSE, sizeof(NSVec3Df), 0);
	glDrawArrays(GL_LINES, 0, verts.size());

	glDisableVertexAttribArray(POS_ATT);
}

void NSBoundingBox::_drawOBB(const NSMatrix4Df & transform)
{
	NSMatrix4Df trans = transform;

	glBindBuffer(GL_ARRAY_BUFFER, transformBuf);
	glBufferData(GL_ARRAY_BUFFER, sizeof(NSMatrix4Df), trans.getDataPtr(), GL_DYNAMIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER, positionBuf);
	glEnableVertexAttribArray(POS_ATT);
	glVertexAttribPointer(POS_ATT, 3, GL_FLOAT, GL_FALSE, sizeof(NSVec3Df), 0);

	glBindBuffer(GL_ARRAY_BUFFER, transformBuf);
	glEnableVertexAttribArray(TRANS1_ATT);
	glVertexAttribPointer(TRANS1_ATT, 4, GL_FLOAT, GL_FALSE, sizeof(NSMatrix4Df), 0);
	glVertexAttribDivisor(TRANS1_ATT, 1);

	glEnableVertexAttribArray(TRANS2_ATT);
	glVertexAttribPointer(TRANS2_ATT, 4, GL_FLOAT, GL_FALSE, sizeof(NSMatrix4Df), (const GLvoid*)(sizeof(NSVec4Df)));
	glVertexAttribDivisor(TRANS2_ATT, 1);

	glEnableVertexAttribArray(TRANS3_ATT);
	glVertexAttribPointer(TRANS3_ATT, 4, GL_FLOAT, GL_FALSE, sizeof(NSMatrix4Df), (const GLvoid*)(sizeof(NSVec4Df) * 2));
	glVertexAttribDivisor(TRANS3_ATT, 1);

	glEnableVertexAttribArray(TRANS4_ATT);
	glVertexAttribPointer(TRANS4_ATT, 4, GL_FLOAT, GL_FALSE, sizeof(NSMatrix4Df), (const GLvoid*)(sizeof(NSVec4Df) * 3));
	glVertexAttribDivisor(TRANS4_ATT, 1);

	glDrawArrays(GL_LINES, 0, verts.size());

	glDisableVertexAttribArray(POS_ATT);
	glDisableVertexAttribArray(TRANS1_ATT);
	glDisableVertexAttribArray(TRANS2_ATT);
	glDisableVertexAttribArray(TRANS3_ATT);
	glDisableVertexAttribArray(TRANS4_ATT);
}